'''
Helper for release operations
'''
import csv
from functools import reduce
import json
import logging
import os

import pymysql


def create_sql_template(start, end, colnames, keyword):
    '''Sql template with blank table name'''
    return ' '.join(['SELECT', ','.join(colnames), 'FROM %s WHERE',
                     '%s >= %d AND %s < %d' % (keyword, start, keyword, end)])


class ReleaseHelper():
    '''Helper for sharing arguments'''

    logger = logging.getLogger('release')

    __slots__ = ['task', 'producer', 'conn', 'storage']

    def __init__(self, task, producer, conn, storage):
        '''
        task: release task
        producer: message producer
        db: database connection
        storage: path to store history date
        '''
        self.task = task
        self.producer = producer
        self.conn = conn
        self.storage = storage

    @property
    def keyword(self):
        '''Column name of timestamp'''
        return self.task.time_column

    @property
    def tables(self):
        '''Related tables'''
        return self.task.tables

    @property
    def colnames(self):
        '''Column names'''
        return self.task.colnames

    @property
    def topic(self):
        '''Target topic'''
        return self.task.topic

    def json_query(self, sql):
        '''Query database with dict result'''
        with self.conn.cursor(pymysql.cursors.DictCursor) as cur:
            count = cur.execute(sql)
            ret = cur.fetchall()
        return ret, count

    def release_data(self, data):
        '''Release data to kafka'''
        self.producer.send(self.topic, json.dumps(data).encode('utf-8'))

    def release_csv_data(self, table, data, date):
        '''Record data into csv'''
        path = os.path.join(self.storage, date, self.task.classification)
        os.makedirs(path, exist_ok=True)
        path = os.path.join(path, '%s.csv' % (table, ))
        is_new = not os.path.exists(path)
        with open(path, 'a') as obj:
            writer = csv.DictWriter(obj, fieldnames=self.colnames)
            if is_new:
                writer.writeheader()
            for row in data:
                writer.writerow(row)


class IndexReleaseHelper(ReleaseHelper):
    '''Helper for releasing indices'''

    def _merge_by_time(self, accumulator, item):
        timestamp = item[self.keyword]
        if timestamp in accumulator:
            accumulator[timestamp].append(item)
        else:
            accumulator[timestamp] = [item, ]
        return accumulator

    def release(self, drift, date, sql_template):
        '''
        Release data, and merge data with same timestamp

        sql_template: Sql temaplte with blank table name
        '''
        data = {}
        timestamps = set()
        count = 0
        # 1. Query
        for table in self.tables:
            data[table], num = self.json_query(sql_template % (table, ))
            count += num
            for item in data[table]:
                item[self.keyword] += drift
                timestamps.add(item[self.keyword])

        # 2. Record into csv
        for table in self.tables:
            self.release_csv_data(table, data=data[table], date=date)

        # 3. Release to kafka
        data = {table: reduce(self._merge_by_time, data[table], {})
                for table in self.tables}
        for timestamp in sorted(timestamps):
            events = {}
            for table in self.tables:
                events[table] = data[table].get(timestamp, [])
            body = {
                self.keyword: timestamp,
                'body': events,
            }
            self.release_data(data=body)

        self.logger.info('Release %d events for topic "%s": %s',
                         count, self.topic, ','.join(self.tables))


class TraceReleaseHelper(ReleaseHelper):
    '''Helper for releasing traces'''

    identifier = 'callType'

    @property
    def colnames(self):
        return [self.identifier, ] + self.task.colnames

    def release(self, drift, date, sql_template):
        '''
        Release data without merge

        sql_template: Sql temaplte with blank table name
        '''
        count = 0
        for table in self.tables:
            data, num = self.json_query(sql_template % (table, ))
            count += num
            for item in data:
                item[self.keyword] += drift
                item[self.identifier] = self.tables[table]
                self.release_data(data=item)
            self.release_csv_data(table, data=data, date=date)

        self.logger.info('Release %d events for topic "%s": %s',
                         count, self.topic, ','.join(self.tables))


def release_factory(task, *args, **kwargs):
    '''Create ReleaseHelper instance based on task.topic'''
    if task.topic == 'trace':
        return TraceReleaseHelper(task, *args, **kwargs)

    # platform index or business index
    return IndexReleaseHelper(task, *args, **kwargs)
